package com.jd.jstable.aop;

import com.jd.jstable.annotation.HighAvailability;
import com.jd.jstable.annotation.HighPerformance;
import com.jd.jstable.annotation.Reset;
import com.jd.jstable.config.KeyMode;
import com.jd.jstable.strategy.Pair;
import com.jd.jstable.strategy.ResultOrientedStrategy;
import com.jd.jstable.strategy.StrategyUtil;
import com.jd.jstable.utils.ReflectionUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;

/**
 * A processor to provide a backup strategy
 * when methods annotated with the com.jd.jstable.annotation.HighAvailability invoked failed
 * 当被com.jd.jstable.annotation.HighAvailability注解的函数调用失败时，将会由Processor提供
 * 备选的策略提供该函数的返回值
 * Created by chenyipu on 2018/1/10.
 */

@Component
@Aspect
public class JStableProcessor {
    public JStableProcessor() {
        logger.info(String.format("Processor Load Ready"));
    }

    private static final Log logger = LogFactory.getLog(JStableProcessor.class);

    @Around("@annotation(com.jd.jstable.annotation.HighPerformance)")
    public Object highPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        //get strategies

        Method method = ReflectionUtil.findMethodByName(target.getClass(), methodName);
        HighPerformance highPerformance = method.getDeclaredAnnotation(HighPerformance.class);
        KeyMode keyMode = new KeyMode(highPerformance.key_mode(), highPerformance.key_index(), highPerformance.prefix());
        List<Pair> list = StrategyUtil.extractStrategies(highPerformance.strategies());
        Object objectSolve = null;
        //pre checked and if necessary ,return the cached value directly.
        try {
            //invoke emergency strategy chain
            objectSolve = StrategyUtil.solve(list, target, method, args, keyMode);
        } catch (Exception e) {
            logger.error("High Performance Solve Failed.", e);
        }
        if (objectSolve != null) {
            return objectSolve;
        }
        //if original method invocation failed, exception should be thrown.
        Object result = joinPoint.proceed(args);
        //if necessary, feed with the result.
        if (null != result) {
            //if necessary, feed with the result.
            StrategyUtil.feed(list, target, method, args, keyMode, result);
        }
        return result;
    }

    @Around("@annotation(com.jd.jstable.annotation.HighAvailability)")
    public Object highAvailability(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        //get strategies

        Method method = ReflectionUtil.findMethodByName(target.getClass(), methodName);
        HighAvailability highAvailability = method.getDeclaredAnnotation(HighAvailability.class);
        KeyMode keyMode = new KeyMode(highAvailability.key_mode(), highAvailability.key_index(), highAvailability.prefix());
        List<Pair> list = StrategyUtil.extractStrategies(highAvailability.strategies());
        //pre checked and if necessary ,return the cached value directly.
        Object result;
        try {
            result = joinPoint.proceed(args);
        } catch (Exception originE) {
            logger.error("High Availability Config Started.", originE);
            Object counsellorAnswer = StrategyUtil.solve(list, target, method, args, keyMode);
            if (counsellorAnswer == null) {
                //no answer. exception would be thrown.
                throw originE;
            } else return counsellorAnswer;
        }
        if (null != result) {
            //if necessary, feed with the result.
            StrategyUtil.feed(list, target, method, args, keyMode, result);
        }
        return result;
    }
    @Around("@annotation(com.jd.jstable.annotation.Reset)")
    public Object reset(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object target = joinPoint.getTarget();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        //get strategies
        Method method = ReflectionUtil.findMethodByName(target.getClass(), methodName);
        Reset reset = method.getDeclaredAnnotation(Reset.class);
        //1.find strategies
        KeyMode keyMode = new KeyMode(KeyMode.SOME_ARG, reset.key_index(), reset.prefix());
        List<Pair> list = StrategyUtil.extractStrategies(reset.strategies());

        StrategyUtil.reset(list, target, method, args, keyMode);

        return joinPoint.proceed(args);
    }

}
